
# include "dfxVertexManager_OGL.h"
# include "dfxLog.h"
# include "dfxRenderData_OGL.h"
# include "dfxGraphicsDevice_OGL.h"

namespace DFX
{
    VertexCache_OGL::VertexCache_OGL(VertexManager_OGL *manager, UInt32 formatId, UInt32 maxNumVertices, 
        UInt32 maxNumIndices, UInt32 vertexSize, UInt32 indexSize)
        : mManager(manager),
        mFormatId(formatId),
        mMaxNumVertices(maxNumVertices),
        mMaxNumIndices(maxNumIndices),
        mVertexSize(vertexSize)
    {
        glGenVertexArrays(1, &mVAOHandle);

        if (!mVAOHandle)
            DFX_ERROR("Unable to generate vertex array");

        glBindVertexArray(mVAOHandle);
        glGenBuffers(1, &mVBHandle);

        if (!mVBHandle)
        {
            DFX_ERROR("Unable to generate vertex buffer");
            return;
        }

        VertexFormat *format = mManager->GetVertexFormat(formatId);

        if (!format)
        {
            DFX_ERROR("Invalid vertex format passed");
            return;
        }

        for (UInt32 i = 0; i < format->GetNumElements(); i++)
        {
            VertexElement *ele = format->GetElement(i);

            glVertexAttribPointer(ele->GetIndex(), ele->GetSize() / 4, GraphicsDevice_OGL::VertexElementFormat[ele->GetFormat()],
                GL_FALSE, format->GetVertexSize(), (const GLvoid*)ele->GetOffset());

            glEnableVertexAttribArray(ele->GetIndex());
        }

        glBufferData(GL_ARRAY_BUFFER, format->GetVertexSize() * mMaxNumVertices,
            NULL, GL_DYNAMIC_DRAW);

        glGenBuffers(1, &mIBHandle);

        if (!mIBHandle)
        {
            DFX_ERROR("Unable to generate index buffer");
            return;
        }        

        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize * mMaxNumIndices,
            NULL, GL_DYNAMIC_DRAW);        

        glBindVertexArray(0);

        mIndexType = (indexSize == 2) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
    }

    VertexCache_OGL::~VertexCache_OGL()
    {
        if (mVBHandle)	glDeleteBuffers(1, &mVBHandle);
        if (mIBHandle)	glDeleteBuffers(1, &mIBHandle);

        if (mVAOHandle)
            glDeleteVertexArrays(1, &mVAOHandle);

        mVAOHandle = 0;
        mVBHandle = 0;
        mIBHandle = 0;
    }

    void VertexCache_OGL::SetMaterial(Material *material)
    {
        if (!UsesMaterial(material))
        {
            if (!IsEmpty())
                Render();

            mManager->InvalidateStates();
            mMaterial = material;
        }
    }

    DFXRESULT VertexCache_OGL::Add(UInt32 numVertices, UInt32 numIndices, const void *vertices, const void *indices)
    {
        GLubyte *tmpVertices = NULL;
        GLubyte *tmpIndices = NULL;

        GLuint posV = 0;
        GLuint posI = 0;
        GLuint indexSize = (mIndexType == GL_UNSIGNED_SHORT) ? 2 : 4;
        
        GLint sizeV = mVertexSize * numVertices;
        GLint sizeI = indexSize * numIndices;

        if (numVertices > mMaxNumVertices || numIndices > mMaxNumIndices)
        {
            DFX_ERROR("Invalid number of vertices/indices passed");
            return DFX_BUFFERSIZE;
        }

        if ((mNumVertices + numVertices) > mMaxNumVertices ||
            (mNumIndices + numIndices) > mMaxNumIndices)
        {
            if (DFX_FAILED(Render()))
            {
                DFX_ERROR("Unable to render cache");
                return DFX_ERROR0;
            }
        }

        if (mNumVertices > 0)
        {
            posV = mVertexSize * mNumVertices;
            posI = indexSize * mNumIndices;
        }

        glBindVertexArray(mVAOHandle);
        glBindBuffer(GL_ARRAY_BUFFER, mVBHandle);

        tmpVertices = (GLubyte*)glMapBufferRange(GL_ARRAY_BUFFER, posV, sizeV, GL_WRITE_ONLY);

        if (!tmpVertices)
        {
            DFX_ERROR("Unable to map vertex buffer");
            return DFX_BUFFERMAP;
        }

        memcpy(tmpVertices, vertices, sizeV);
        glUnmapBuffer(GL_ARRAY_BUFFER);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIBHandle);
        tmpIndices = (GLubyte*)glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, posI, sizeI, GL_WRITE_ONLY);

        if (!tmpIndices)
        {
            DFX_ERROR("Unable to map index buffer");
            return DFX_BUFFERMAP;
        }
        
        GLuint base = mNumVertices;
        numIndices = (numIndices == 0) ? numVertices : numIndices;

        if (mIndexType == GL_UNSIGNED_SHORT)
        {
            GLushort *sIndices = (GLushort*)indices;
            GLushort *stmpIndices = (GLushort*)tmpIndices;

            for (UInt32 i = 0; i < numIndices; i++)
            {
                if (sIndices != NULL)
                    stmpIndices[i] = sIndices[i] + base;
                else
                    stmpIndices[i] = i + base;
            }

            mNumIndices++;
        }
        else
        {
            GLuint *iIndices = (GLuint*)indices;
            GLuint *itmpIndices = (GLuint*)tmpIndices;

            for (UInt32 i = 0; i < numIndices; i++)
            {
                if (iIndices != NULL)
                    itmpIndices[i] = iIndices[i] + base;
                else
                    itmpIndices[i] = i + base;
            }

            mNumIndices++;
        }

        glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
        mNumVertices += numVertices;

        return DFX_SUCCESS;
    }

    DFXRESULT VertexCache_OGL::Render()
    {
        if (mNumVertices <= 0)
            return DFX_SUCCESS;

        if (mManager->GetActiveFormatId() != mFormatId)
        {
            glBindVertexArray(mVAOHandle);
            mManager->SetActiveFormatId(mFormatId);
        }

        mManager->GetGraphicsDevice()->SetMaterial(mMaterial);

        if (DFX_SUCCEEDED(mMaterial->Begin()))
        {
            switch (mManager->GetPrimitiveType())            
            {
            case PRIMITIVE_TYPE_POINT_LIST:
                {
                    glDrawArrays(GL_POINTS, 0, mNumVertices);
                }
                break;

            case PRIMITIVE_TYPE_LINE_LIST:
                {
                    glDrawElements(GL_LINE, mNumIndices / 2, mIndexType, ((GLubyte *)NULL + (0)));
                }
                break;

            case PRIMITIVE_TYPE_LINE_STRIP:
                {
                    glDrawElements(GL_LINE_STRIP, mNumVertices, mIndexType, ((GLubyte *)NULL + (0)));
                }
                break;

            default:
                {
                    glDrawElements(GL_TRIANGLES, mNumIndices / 3, mIndexType, ((GLubyte *)NULL + (0)));
                }
                break;
            }

            mMaterial->End();
        }

        mNumVertices = 0;
        mNumIndices = 0;

        return DFX_SUCCESS;
    }

    VertexManager_OGL::VertexManager_OGL(GraphicsDevice *gd, UInt32 numCaches)
        : VertexManager(gd, numCaches)
    {
    }

    VertexManager_OGL::~VertexManager_OGL()
    {
        VertexCacheIterator it = mVertexCaches.begin();

        while (it != mVertexCaches.end())
        {
            SAFE_DELETE((*it));
            it++;
        }

        mVertexCaches.clear();
        mVertexFormats.clear();

        RenderDataIterator rit = mRenderDatas.begin();

        while (rit != mRenderDatas.end())
        {
            SAFE_DELETE((*rit));
            rit++;
        }

        mRenderDatas.clear();
    }

    VertexFormat* VertexManager_OGL::GetVertexFormat(UInt32 formatId)
    {
        if (formatId < mVertexFormats.size())
            return mVertexFormats[formatId];

        return NULL;
    }

    DFXRESULT VertexManager_OGL::AddVertexFormat(VertexFormat *format, UInt32 *formatId, UInt32 maxNumVertices, UInt32 maxNumIndices)
    {
        VertexFormatIterator it = std::find(mVertexFormats.begin(), mVertexFormats.end(), format);
        
        if (it != mVertexFormats.end())
            return DFX_ERROR0;

        mVertexFormats.push_back(format);        
        
        if (formatId)
            *formatId = mVertexFormats.size() - 1;

        for (UInt32 i = 0; i < mNumCaches; i++)
        {
            VertexCache_OGL *cache = new VertexCache_OGL(this, mVertexFormats.size() - 1, maxNumVertices,
                maxNumIndices, format->GetVertexSize(), sizeof(UInt32));

            mVertexCaches.push_back(cache);
        }

        return DFX_SUCCESS;
    }

    DFXRESULT VertexManager_OGL::CreateBuffer(UInt32 formatId, Material *material, UInt32 numVertices, UInt32 numIndices, 
        UInt32 indexSize, const void *vertices, const void *indices, UInt32 *bufferId)
    {
        char name[MAXBUF];

        if (formatId >= mVertexFormats.size())
            return DFX_CREATEFAILED;

        sprintf(name, "Buffer_%002d", mRenderDatas.size());
        RenderData *data = mGraphicsDevice->CreateRenderData(name);

        if (DFX_FAILED(data->CreateVertexBuffer(BUFFER_USAGE_STATIC, mVertexFormats[formatId], numVertices, (void*)vertices)))
        {
            SAFE_DELETE(data);
            return DFX_CREATEFAILED;
        }

        if (DFX_FAILED(data->CreateIndexBuffer(BUFFER_USAGE_STATIC, indexSize, numIndices, (void*)indices)))
        {
            SAFE_DELETE(data);
            return DFX_CREATEFAILED;
        }

        mRenderDatas.push_back(data);
        mMaterials.push_back(material);

        if (bufferId)
            *bufferId = mRenderDatas.size() - 1;

        return DFX_SUCCESS;
    }

    DFXRESULT VertexManager_OGL::Render(UInt32 formatId, Material *material, UInt32 numVertices,
        UInt32 numIndices, const void *vertices, const void *indices)
    {
        VertexCache_OGL *cache = NULL;
        VertexCache_OGL *emptyCache = NULL;
        VertexCache_OGL *fullestCache = NULL;

        if (formatId >= mVertexCaches.size() / mNumCaches)
        {
            DFX_ERROR("Invalid format id: %d", formatId);
            return DFX_ERROR0;
        }

        fullestCache = mVertexCaches[formatId * mNumCaches];
        mActiveFormatId = MAX_ID;

        for (UInt32 i = 0; i < mNumCaches; i++)
        {
            UInt32 index = formatId * mNumCaches + i;

            if (mVertexCaches[i]->UsesMaterial(material))
                return mVertexCaches[i]->Add(numVertices, numIndices, vertices, indices);

            if (mVertexCaches[i]->IsEmpty())
                emptyCache = mVertexCaches[i];

            if (mVertexCaches[i]->GetNumVertices() > fullestCache->GetNumVertices())
                fullestCache = mVertexCaches[i];
        }

        if (emptyCache != NULL)
        {
            emptyCache->SetMaterial(material);
            return emptyCache->Add(numVertices, numIndices, vertices, indices);
        }

        fullestCache->Render();
        fullestCache->SetMaterial(material);

        return fullestCache->Add(numVertices, numIndices, vertices, indices);
    }

    DFXRESULT VertexManager_OGL::RenderVertices(UInt32 numVertices, const void *vertices, Material *material)
    {
        return DFX_SUCCESS;
    }

    DFXRESULT VertexManager_OGL::Render(UInt32 bufferId)
    {
        if (bufferId >= mRenderDatas.size())
        {
            DFX_ERROR("Invalid buffer id: %d passed", bufferId);
            return DFX_RENDERFAILED;
        }      

        if (mMaterials[bufferId])
        {
            mGraphicsDevice->SetRenderData(mRenderDatas[bufferId]);
            mGraphicsDevice->SetMaterial(mMaterials[bufferId]);

            if (DFX_SUCCEEDED(mMaterials[bufferId]->Begin()))
            {
                mRenderDatas[bufferId]->Draw(mPrimitiveType);
                mMaterials[bufferId]->End();
            }
        }

        return DFX_SUCCESS;
    }

    DFXRESULT VertexManager_OGL::Render(UInt32 bufferId, Material *material, UInt32 startIndex, UInt32 numVertices, UInt32 numTris)
    {
        if (bufferId >= mRenderDatas.size())
        {
            DFX_ERROR("Invalid buffer id: %d passed", bufferId);
            return DFX_RENDERFAILED;
        }

        if (material)
        {
            mGraphicsDevice->SetRenderData(mRenderDatas[bufferId]);
            mGraphicsDevice->SetMaterial(material);

            if (DFX_SUCCEEDED(material->Begin()))
            {
                mRenderDatas[bufferId]->Draw(mPrimitiveType, startIndex, 
                    mRenderDatas[bufferId]->GetNumIndices(), numTris);

                material->End();
            }
        }

        return DFX_SUCCESS;
    }

    DFXRESULT VertexManager_OGL::RenderPoints(UInt32 formatId, UInt32 numVertices, const void *vertices, const Color4 &color)
    {
        return DFX_SUCCESS;
    }

    DFXRESULT VertexManager_OGL::RenderLines(UInt32 formatId, UInt32 numVertices, const void *vertices, const Color4 &color)
    {
        return DFX_SUCCESS;
    }

    DFXRESULT VertexManager_OGL::RenderLine(const Vector3 &start, const Vector3 &end, const Color4 &color)
    {
        return DFX_SUCCESS;
    }

    DFXRESULT VertexManager_OGL::ForcedRender(UInt32 formatId)
    {
        DFXRESULT dr = DFX_SUCCESS;

        if (formatId >= mVertexCaches.size() / mNumCaches)
        {
            DFX_ERROR("Invalid format id: %d", formatId);
            return DFX_ERROR0;
        }

        for (UInt32 i = 0; i < mNumCaches; i++)
        {
            UInt32 index = formatId * mNumCaches + i;

            if (DFX_FAILED(mVertexCaches[index]->Render()))
            {
                DFX_ERROR("Error rendering cache: %d for format: %d", i, formatId);
                dr = DFX_ERROR0;
            }
        }

        return dr;
    }

    DFXRESULT VertexManager_OGL::ForcedRenderAll()
    {
        VertexCacheIterator it = mVertexCaches.begin();
        DFXRESULT dr = DFX_SUCCESS;

        while (it != mVertexCaches.end())
        {
            if (DFX_FAILED((*it)->Render()))            
                dr = DFX_RENDERFAILED;            
        }

        return dr;
    }

    void VertexManager_OGL::InvalidateStates()
    {
        mActiveFormatId = MAX_ID;
    }
}